#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
和代码原始效果有　很大区别？？
　为啥？？？
"""
import sys
import glob
import os
import os.path
import cv2
import numpy as np
import math
import sympy as sp
import mpmath as mp



class ClockScale(object):
    def __init__(self, stepName, config):
        self.config = config

    def clear_debug(self):
        filelist = glob.glob("output/*.jpg")
        for f in filelist:
            os.remove(f)

    def write_debug(self,img, name):
        cv2.imwrite(f"output/{name}.jpg", img)

    def output_circles(self,img, circles):
        print("output_circles圆形个数=", circles.__len__())
        for idx, dial in enumerate(circles):
            x, y, radius = dial
            # draw the outer circle
            cv2.circle(img, (x, y), radius, (0, 255, 0), 2)
            # draw the center of the circle
            cv2.circle(img, (x, y), 2, (0, 0, 255), 3)
        self.write_debug(img, "AllCircles")
        cv2.imshow("AllCircles", img)
        pass

    def get_point_line_distance(self, point, line):
        point_x = point[0]
        point_y = point[1]
        line_s_x = line[0][0]
        line_s_y = line[0][1]
        line_e_x = line[1][0]
        line_e_y = line[1][1]
        # 若直线与y轴平行，则距离为点的x坐标与直线上任意一点的x坐标差值的绝对值
        if line_e_x - line_s_x == 0:
            return math.fabs(point_x - line_s_x)
        # 若直线与x轴平行，则距离为点的y坐标与直线上任意一点的y坐标差值的绝对值
        if line_e_y - line_s_y == 0:
            return math.fabs(point_y - line_s_y)
        # 斜率
        k = (line_e_y - line_s_y) / (line_e_x - line_s_x)
        # 截距
        b = line_s_y - k * line_s_x
        # 带入公式得到距离dis
        dis = math.fabs(k * point_x - point_y + b) / math.pow(k * k + 1, 0.5)
        return dis

    def Try_These_Circles(self,original, circles):
        #　找到　多　个　圆形？？
        dials = np.uint16(np.around(circles))[0, :]
        ##按照半径大小排序：　从大到小的半径；
        sorted_dials = sorted(dials, reverse=True, key=lambda dial: dial[2])
        allCircle_img = original.copy()
        self.output_circles(allCircle_img, sorted_dials)
        result = ""
        ##会找到　多个的　圆形盘？　最佳的是　？　适当的且 是 最大圆盘
        for idx, dial in enumerate(sorted_dials):
            x, y, radius = dial
            # 圆盘中心点  没有完整的园 ，不合格！
            if x >= radius and y >= radius:
                ##直接截取 圆形的哪一个外包为正方形图片。
                lower_y = y - radius
                lower_x = x - radius
                # 从原图截取圆环区域的。
                dial_img = original[lower_y:y + radius, lower_x:x + radius].copy()
                value = self.judgeNeedleAngle(dial_img, idx)
                ##已经读到了正常的数据就终止。　否则要继续观察下一个　圆形可能的轮廓，不完整的轮廓园也会检测出来。
                if value is not None:
                    circle_img = original.copy()
                    # draw the outer circle
                    cv2.circle(circle_img, (x, y), radius, (0, 255, 0), 2)
                    # draw the center of the circle
                    cv2.circle(circle_img, (x, y), 2, (0, 0, 255), 3)
                    self.write_debug(circle_img, "circles")
                    print('Loop:从园盘图output中间序号第' + str(idx) + '个获得：')
                    result += str(value)
                    break

        if len(result) <= 0:
            print("Loop:识别失败，没有读到合格的刻度数值", result)
            return  None
        else:
            print("Loop:读到的刻度数＝", result)
            return  result
        pass

    def read_Scale(self):
        #要找的模板
        self.clear_debug()

        filename = sys.argv[1] if len(sys.argv) > 1 else ""
        ##上一个步骤的 输出文件= =截取中间的仪表仪器图片。
        filename = 'stepOut/cutout-sourceImg.jpg'
        if not os.path.exists(filename):
            print("Usage: python3 power-meter-reader.py <image>")
            exit(1)
        ##已经按照仪表仪器最小匹配的模板图片自动截取抠出的已经捕获对象小图。
        original = cv2.imread(filename)
        #self.write_debug(original, "original")

        originalSize = original.shape[:2]
        # 图片尺寸归一化
        # resizedSize = (int(originalSize[1] * 0.3), int(originalSize[0] * 0.3))
        # resized = cv2.resize(original, resizedSize)
        # write_debug(resized, "resized")

        gray = cv2.cvtColor(original, cv2.COLOR_BGR2GRAY)

        blurred = cv2.GaussianBlur(gray, (5, 5), 0)
        #self.write_debug(blurred, "blurred")
        rows_b, cols_b = blurred.shape
        outValue=None
        # 和图片的尺寸　还有很大关系的！！
        for param2 in range(140, 70, -5):
            # HoughCircles(第四个参数特别重要minDist太小了那么检出圆形太多。第三个参数dp=1:累加器图像的反比分辨；
            ##按照先前经验　来设置第四个参数minDist=20　；　 param1=100, param2=43, minRadius=40, maxRadius=70
            # param1=100是Canny边缘函数的高阈值(Canny下限阈值固定是param1的1/2)；  param1=强边界点{像素累积？}=越小的越会失去宏观只会看见微观的。
            # param2参数和Canny无关！！ 越小，检测出圆越多!param_2根据你的图像中的圆大小设置，　param2代表累积能够组成一个圆像素数数量。
            # circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, 1, 20, np.array([]), param1=100, param2=45, minRadius=int(cols_b*0.38))
            circles = cv2.HoughCircles(blurred, cv2.HOUGH_GRADIENT, 1, 1, param1=100,
                                       param2=param2, minRadius=int(cols_b * 0.15), maxRadius=int(cols_b * 0.38))

            ##如果上一个步骤切取得仪表圆盘是不正常的，比如多个冗余部件，或者透视变换角度倾斜厉害的/不正常矩形形状的，都可能找不到合格的园！！
            if circles is not None:
                outValue = self.Try_These_Circles(original, circles)
                if outValue is not None:
                    break

        if outValue is None:
            print("模板有问题，没能获取合格的刻度数！")
            return None

    #参数offset=模板上起始刻度放射线相对于Y轴的旋转角，顺时针排列刻度为０刻度偏转角度，逆时针为负数。  clockwise=指针标注数字正好反了，True=顺时针;
    #def judgeNeedleAngle(config, idx, img, offset=0, clockwise=True):
    def judgeNeedleAngle(self,img, idx):
        offset = self.config["dialsOffset"]
        clockwise = self.config["clockwise"]
        #offset, clockwise = config
        offset_r = offset * (np.pi / 180)      ##偏转角度，0基准刻度线角度位置。

        height, width = img.shape[:2]
        print("待识别圆盘图　高度　宽度＝", height, width)
        self.write_debug(img, f"CircleForNeedle")
        ##指针的旋转中心点，就是圆形截图图片中心
        center = [width / 2, height / 2]
        radius = int(width / 2)
        circle = sp.Circle(sp.Point(center), radius)

        #offset_ray = sp.Ray(sp.Point(center), angle=mp.radians(offset))
        offset_img = img.copy()
        origin_point = [ center[0], 0 ]
        ##画出 基准的0 刻度位置的 放射线。
        offset_point = [
          math.cos(offset_r) * (origin_point[0] - center[0]) - math.sin(offset_r) * (origin_point[1] - center[1]) + center[0],
          math.sin(offset_r) * (origin_point[0] - center[0]) + math.cos(offset_r) * (origin_point[1] - center[1]) + center[1]
        ]
        cv2.line(offset_img, (int(center[0]), int(center[1])), (int(offset_point[0]), int(offset_point[1])), (0, 255, 0), 3)
        if  clockwise:
            offset_Lg = (offset + self.config["IntersectionAngle"]) * (np.pi / 180)     ##MAX刻度线角度位置。
        else:
            offset_Lg = (offset - self.config["IntersectionAngle"]) * (np.pi / 180)
        ##刻度最大位 的射线
        offset_point = [
          math.cos(offset_Lg) * (origin_point[0] - center[0]) - math.sin(offset_Lg) * (origin_point[1] - center[1]) + center[0],
          math.sin(offset_Lg) * (origin_point[0] - center[0]) + math.cos(offset_Lg) * (origin_point[1] - center[1]) + center[1]
        ]
        cv2.line(offset_img, (int(center[0]), int(center[1])), (int(offset_point[0]), int(offset_point[1])),
                 (0, 0, 255), 3)

        self.write_debug(offset_img, f"RadialLineX-{idx}")

        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

        ##新增加 begin
        ## 图像二值化   ; Otsu阈值
        _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_TOZERO + cv2.THRESH_OTSU)
        self.write_debug(binary, f"L-Binary-{idx}")
        ### 中值滤波去噪
        #median = cv2.medianBlur(binary, 1)          # 中值滤波
        #write_debug(median, f"L-medianBlur-{idx}")
        ##新增加 end

        # 使用3x3结构元素进行 腐蚀操作
        se = np.ones((5, 5), dtype=np.uint8)
        #腐蚀  膨胀,  黑白低色正好颠倒了！ cv2.erode　相反
        erode = cv2.dilate(binary, se, None, (-1, -1), 1)
        self.write_debug(erode, f"L-Erode-{idx}")

        ##滤波； 高斯模糊  ksize(3,3) / (5,5)指定了高斯模糊的半径  ,除去噪声
        blurred = cv2.GaussianBlur(erode, (3, 3), 0)
        ##blurred = cv2.GaussianBlur(gray, (3,3), 0)    #新增加--修改
        self.write_debug(blurred, f"L-GaussianBlur-{idx}")

        #edges = cv2.Canny(blurred, 30, 130)
        #threshold1=下限阈值，如果像素梯度低于下限阈值，则将像素不被认为边缘，threshold2=上限阈值，如果像素梯度高于上限阈值，则将像素被认为是边缘
        #参数apertureSize为Sobel运算提供内核大小，默认值为3, 这个apertureSize越大！就会显示边缘越多了！
        #参数threshold1越小的，能被检测出的细小细节部分结构部件的边缘就越可能变长了。  threshold2越大被检测出来的部件个数越少。
        edges = cv2.Canny(blurred, 50, 100, apertureSize=3)
        #edges = cv2.Canny(blurred, 50, 200)
        self.write_debug(edges, f"ptr-Canny-{idx}")

        #[表指针的直线段] 查找最佳的 可能指针线段。
        edge = self.searchCenterRayline(edges, img ,idx, minLineLength=radius*.28)
        ##参数没调好 就找不到；   edges-4.jpg 基本没剩下什么轮廓了。
        if edge is  None:
            print("没能探知合格的指针线段，idx=",idx)
            return None   ##没找到指针
        hand_edge_img = img.copy()
        cv2.line(hand_edge_img, (edge[0], edge[1]), (edge[2], edge[3]), (255, 0, 0), 3)
        self.write_debug(hand_edge_img, f"OK-Line-{idx}")

        hand_ray = self.generate_hand_ray(center, edge)
        circle_intersection = hand_ray.intersection(circle)[0]

        cv2.line(img, (int(center[0]), int(center[1])), (int(circle_intersection.x), int(circle_intersection.y)), (0, 0, 255), 2)
        self.write_debug(img, f"Return-Angle-{idx}")

        angle_r = math.atan2(circle_intersection.y - center[1], circle_intersection.x - center[0]) - math.atan2(origin_point[1] - center[1], origin_point[0] - center[0])
        angle = angle_r * 180 / np.pi
        #按照　指针线段末端远端点　和圆心点的直线角度，计算刻度数据
        ##表盘不是水平安放的， 倾斜 offset角度 的基准表盘。
        MaxValue=self.config["MaxValue"]
        MinValue=self.config["MinValue"]

        angle =angle-offset

        if angle < 0:
            angle = 360 + angle

        ##映射数字： 360度 　一圈　　 0-10的刻度范围。
        #打气筒　260度　映射　0-100刻度；　
        angle_p = angle/self.config["IntersectionAngle"]
        if not clockwise:
            angle_p = 1 - angle_p
        value = MaxValue*angle_p
        ##允许误差范围 +- 5%的角度范围；
        if value >= MinValue - MaxValue*0.08  and  value <= MaxValue *1.08:
            cv2.imshow("Return-Angle-"+str(idx), img)
            return value        ##最大刻度数100
        pass

    # turn the edge of a hand into a ray from the point closest to the centre
    def generate_hand_ray(self,center_point, edge):
        center = sp.Point(center_point)
        first = sp.Point(edge[0:2])
        second = sp.Point(edge[2:4])
        first_dist = center.distance(first)
        second_dist = center.distance(second)

        return sp.Ray(first, second) if first_dist < second_dist else sp.Ray(second, first)

    ##从圆盘截图中搜索：最长的直线段，并且尽量是从中心点出发的射线线段。
    ##查找最佳的 可能线段。　　负反馈：　还需要配合圆心和长度，直线段和原中心点的距离关系。
    def searchCenterRayline(self,edges, source,idx, minLineLength=40):
        cy, cx = source.shape[:2]
        cy=cy/2
        cx=cx/2
        radius=(cx+cy)/2
        ##从最苛刻条件 的开始, 苛刻条件的 检测出的线段 是指针的概率就最大了。
        for threshold in range (120, 30, -3):
            ##threshold逐步降低，越小检测到的线段就越多。　threshold设置的比minLineLength小的话就检测不到了？
            ##maxLineGap太大了许多不是直线段的也能连接变成线段！　　minLineGap:线段上最近两点之间的阈值（交叉覆盖线的宽度）。
            lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=threshold,
                                    minLineLength=minLineLength, maxLineGap=4)
            if lines is not None:
                maxlen=0
                whitch= -1
                for i in range(0, len(lines)):
                    debug_img = source.copy()
                    l = lines[i][0]
                    cv2.line(debug_img, (l[0], l[1]), (l[2], l[3]), (0, 255, 0), 2, cv2.LINE_AA)

                    p1 = np.array([cx, cy])     ##圆的中心点
                    p2 = np.array([l[0], l[1]])
                    p3 = p2 - p1
                    len1 = math.hypot(p3[0], p3[1])
                    p4 = np.array([l[2], l[3]])
                    p5 = p4 - p1
                    len2 = math.hypot(p5[0], p5[1])
                    centerLine=np.minimum(len1, len2)    ##离去圆心点最近距离
                    ##线段靠近圆中心点，　＋　并且该线段延长线能够经过圆心点附近的。
                    offsetCenter = self.get_point_line_distance(p1, [(l[0], l[1]), (l[2], l[3])])
                    ##大约应该从圆形中心附近 发出来的射线。
                    if offsetCenter<= radius*0.033  and  centerLine<= radius*0.18:
                        self.write_debug(debug_img, f"findLine-Y-{idx}_{i}")
                        p6 = p4 - p2
                        lenP = math.hypot(p6[0], p6[1])
                        if lenP>maxlen:      ##最长的线条
                            maxlen=lenP
                            whitch=i
                    else:
                        self.write_debug(debug_img, f"findLine-N-{idx}_{i}")
                if whitch>=0  and  whitch<len(lines):
                    return lines[whitch][0]     ##第一条线段，  多条情况　挑选？
        pass


